#Import modules
from skimage import filters
from skimage import morphology
import pandas as pd
from skimage.measure import label, regionprops, regionprops_table
from skimage import measure
import scipy
import functions_figs as ff
import numpy as np
import copy
import matplotlib.pyplot as plt


def create_post_array_mask2(image_org, crop_border_width=1,area_min_factor=8, area_min_inv=10, otsu_radius = 50, sz_obj_peak_local_max = 11, dilate=False, dil_size=4, disp_details = True, factor_axis_major_length=4, factor_local_otsu=0.8):
    """
      Creates a mask of the post array (100x magnification)

            Parameters:
                    image_org (np array): fluorescent image (median) with posts
                    crop_border_width (float): width of the image border to crop away. Facilitates the processing to make sure no objects are combined at the edges.
                    area_max (float): Threshold that excludes all objects above this pixel area. This is to remove the channel area, 
                    where the fluid moves, that becomes very large after the image has been inverted.
                    small_obj_threshold (float): The threshold (in pixel area) for removal of small objects
                    otsu_radius (float): Radius of the disk to perform local otsu thresholding
                    dilate (bool): Will dilate the final objects if they would be too small.
                    dil_size (float): Size of the disk object to dilate with.

            Returns:
                    image_cropped (np array): Original image, but cropped to match the mask
                    mask (np array): Boolean mask of the post array

        Update:
        change so that the keywords arguments are specified by a dictionary:
            keyword_dict = {'keyword1': value1, 'keyword2': value2}
            function(**keyword_dict)
    """
    return_list_of_images=True
        
    # Generate mask of the posts and of the fluid
    # kwargs = {'crop_border_width':1, 
    #         'area_min':30,
    #         'area_max':20000, 
    #         'small_obj_threshold':30, 
    #         'otsu_radius':4, 
    #         'dilate':False, 
    #         'dil_size':4}
    #  **kwargs
    # for key,val in kwargs.items():
    #     exec(key + '=val')
    # disp_details = True

    if return_list_of_images:
        img_list = []
        titles_list = []
    if return_list_of_images:
        image_org_crop = copy.deepcopy(image_org)
        image_org_crop = ff.crop_border(image_org_crop, b=crop_border_width)
        img_list.append(image_org_crop)
        titles_list.append('Original image')     
    #Raise an exception if the image has the wrong dimensions.
    if len(image_org.shape) != 2:
        raise ValueError(f"The image must be 2D and not {len(image_org.shape)}D")

    sobel_edge = False
    if sobel_edge:
        #Apply a Sobel Edge Detection filter
        img_pre_sobel = copy.deepcopy(img_list[-1])
        image_sobel = filters.sobel(img_pre_sobel)
        if return_list_of_images:
            img_list.append(image_sobel)
            titles_list.append('After Sobel Edge Detection')
        
    #Convert to uint8 (required for Local Otsu Thresholding)
    img_pre_local_otsu = copy.deepcopy(image_org)
    data = np.copy(img_pre_local_otsu)
    data = data.astype(np.float64) / np.max(img_pre_local_otsu) # normalize the data to 0 - 1
    data = 255 * data # Now scale by 255
    img = data.astype(np.uint8)
    
    #Binarization by Local Otsu thresholding    
    selem = morphology.disk(otsu_radius) #disk object
    local_otsu = filters.rank.otsu(img, selem) #threshold mask
    bw_local_otsu = img >= local_otsu*factor_local_otsu 
    if return_list_of_images:
        img_list.append(bw_local_otsu)
        titles_list.append(f'After local Otsu thresholding,\nfactor = {factor_local_otsu}, disk radius = {otsu_radius}')   

    # Clear any small object    
    # bw_pre_cleared = np.invert(copy.deepcopy(img_list[-1]))
    # arr_pre_cleared = bw_pre_cleared > 0 #morphology.remove_small_objects() requires the array to contain labeled objects (0 or 1, not bool)
    # bw_cleared = morphology.remove_small_objects(arr_pre_cleared, min_size=area_min, connectivity=1, in_place=False, ) # clear objects <400 px
    # if return_list_of_images:
    #     img_list.append(np.invert(bw_cleared))
    #     titles_list.append(f'After clearing small objects \nbelow threshold = {round(area_min,0)} pix^2')    
    bw_pre_cleared = copy.deepcopy(img_list[-1])
    arr_pre_cleared = bw_pre_cleared > 0 #morphology.remove_small_objects() requires the array to contain labeled objects (0 or 1, not bool)
    bw_cleared = morphology.remove_small_objects(arr_pre_cleared, min_size=area_min_inv, connectivity=1, in_place=False, ) # clear objects <400 px
    if return_list_of_images:
        img_list.append(bw_cleared)
        titles_list.append(f'After clearing small objects \nbelow area threshold = {round(area_min_inv,0)} pix^2')    

    crop_image = True
    if crop_image:
        #Remove the border by cropping as the posts don't extend to the image border
        bw_pre_cropped = copy.deepcopy(img_list[-1])
        bw_cropped = ff.crop_border(bw_pre_cropped, b=crop_border_width)
        if return_list_of_images:
            img_list.append(bw_cropped)
            titles_list.append(f'After cropping image,\nwidth = {crop_border_width}')

    bw_pre_mask_fluid = copy.deepcopy(img_list[-1])
    mask_fluid = find_mask_fluid(bw_pre_mask_fluid)

    #Image inversion
    bw_pre_inv = copy.deepcopy(img_list[-1])
    bw_inv = np.invert(bw_pre_inv)  
    if return_list_of_images:
        img_list.append(bw_inv)
        titles_list.append('Inverted image')  

    #Extract the objects in the binary image
    labels = measure.label(bw_inv)
    
    #Extract the region properties out of the objects
    props_table = measure.regionprops_table(labels, properties=('area', 'coords', 'major_axis_length'))  
    #Convert the list of the region properties to a pandas data frame
    df = pd.DataFrame(props_table)
    if len(df) == 0:
        raise ValueError(f'Could not find any objects in the image')
    areas = df.area.values
    #lim_upper_area = np.median(areas)+3*np.std(areas) #3 standard deviations above the median value
    area_min = area_min_factor*np.median(areas)

    axis_major_lengths = df.major_axis_length.values
    lim_upper_axis_major_length = factor_axis_major_length*np.median(axis_major_lengths)

    if disp_details: 
        fig = plt.figure(figsize=(8, 2))  # create a figure object
        ax = fig.add_subplot(1, 1, 1)  # create an axes object in the figure
        plt.title('Objects axis major length')
        plt.hist(axis_major_lengths, range=(0,200), bins=256)
        plt.axvline(lim_upper_axis_major_length, color='r', label='max length allowed')
        plt.xlabel('Axis major length [pix^2]')
        plt.ylabel('count')
        plt.legend(loc='best')
        plt.show()

        fig = plt.figure(figsize=(8, 2))  # create a figure object
        ax = fig.add_subplot(1, 1, 1)  # create an axes object in the figure
        plt.title('Objects Area')
        plt.hist(areas, range=(0,200), bins=256)
        # plt.axvline(lim_upper_area, color='r', '')
        plt.axvline(area_min, color='r', label=f'min area allowed\n ({round(area_min)} or {np.round(area_min_factor,1)} x median val.)')
        plt.axvline(np.median(areas), linestyle='--', color='g', label=f'median = {np.round(np.median(areas),2)}')
        plt.xlabel('area [pix^2]')
        plt.ylabel('count')
        plt.legend(loc='best')
        plt.show()

    #Filter maximum area allowed (to remove the gap spacing)    
    # df = df[df['area'] < lim_upper_area]
    df = df[df['major_axis_length'] < lim_upper_axis_major_length]
    df = df[df['area'] > area_min]
    # print_info = True
    # if print_info:
        #print(df['area'].value_counts().head(15))

    #Create a new image (cropped) to copy the coordinates from the adjusted objects data frame
    bw_no_oblong = np.zeros_like(img_list[-1])
    
    #Add the objects to the new image, pixel coordinate by pixel coordinate
    coords = df['coords'].to_numpy()
    for obj in coords:    
        for [x,y] in obj:
            bw_no_oblong[x,y] = 1    
    img_list.append(bw_no_oblong)
    titles_list.append(f'Removed oblong objects \nwith major axis length above \nthreshold = {round(lim_upper_axis_major_length,0)} pix ({factor_axis_major_length} x median val.\n also cleared areas below = {round(area_min,0)} pix^2, factor = {area_min_factor})')  

    # bw_pre_cleared2 = copy.deepcopy(img_list[-1])
    # arr_pre_cleared2 = bw_pre_cleared > 0 #morphology.remove_small_objects() requires the array to contain labeled objects (0 or 1, not bool)
    # bw_cleared2 = morphology.remove_small_objects(arr_pre_cleared2, min_size=area_min, connectivity=1, in_place=False, ) # clear objects <400 px
    # img_list.append(bw_cleared2)
    # titles_list.append(f'After clearing small objects \nbelow area threshold = {round(area_min,0)} pix^2')  
    #Dilate to match the real post size (optional)
    if dilate:
        bw_no_oblong = morphology.binary_dilation(bw_no_oblong, selem=morphology.disk(dil_size))
    
    #Rename the image to be returned        
    mask = bw_no_oblong
    
    #Crop the original image as well
    image_cropped = ff.crop_border(image_org, b=crop_border_width)
    print(f'Creating post array mask, image border cropped by {crop_border_width} pixels ')

    if return_list_of_images:
        # img_list[-1]
        masked_img = ff.mask_gray_img(image_cropped, mask, color=[255, 0, 0], alpha=0.2)
        img_list.append(masked_img)
        titles_list.append(f'Post Mask overlay')  

        img_list.append(mask_fluid)
        titles_list.append(f'Mask of the fluid')         

    if return_list_of_images:
        return image_cropped, mask, mask_fluid, img_list, titles_list
    else:
        return image_cropped, mask

def find_mask_fluid(bw):
    """[Find the boolean mask for the fluid in the array]

    Args:
        bw ([np array, bool]): [Boolean image with the fluid as 1 and the posts and bg as 0.]

    Returns:
        mask_fluid [np array, bool]: [boolean mask for the fluid in the array]
    """
    labels = measure.label(bw)
    #Extract the region properties out of the objects
    props_table = measure.regionprops_table(labels, properties=('area', 'coords'))  

    #Convert the list of the region properties to a pandas data frame
    df_props = pd.DataFrame(props_table)
    if len(df_props) == 0:
        raise ValueError('The mask bw for creating the fluid mask is empty')
    inx_max = df_props['area'].idxmax()
    coords_area_max = df_props.iloc[inx_max,1]

    #Create a new image (cropped) to copy the coordinates from the adjusted objects data frame
    mask_fluid = np.zeros_like(bw, dtype='bool')

    #Add the objects to the new image, pixel coordinate by pixel coordinate
    for [x,y] in coords_area_max:   
        mask_fluid[x,y] = True    

    return mask_fluid

# #Segment objects that appear to be merged
# bw_pre_watershed = copy.deepcopy(img_list[-1])
# labels_watershed = watershed_segment_image(bw_pre_watershed, sz_obj_peak_local_max = sz_obj_peak_local_max, disp_details = disp_details)
# props_table = measure.regionprops_table(labels, properties=('area', 'coords'))   
# df_props = pd.DataFrame(props_table) #Convert the list of the region properties to a pandas data frame

# if return_list_of_images:
#     img_list.append(bw_no_big)
#     titles_list.append(f'Segmented objects \nwith sz_obj_peak_local_max = {round(sz_obj_peak_local_max)} pix')  

import scipy.ndimage
from skimage.segmentation import watershed
from skimage.feature import peak_local_max
import skimage
def watershed_segment_image(bw, disp_details = True, min_distance = 3):
    """[summary]

    Args:
        bw ([type]): [description]
        sz_obj_peak_local_max (int, optional): [description]. Defaults to 11.

    Returns:
        labels [np array, int32]: [Labeled image]

    Based on the great tutorial: http://bebi103.caltech.edu.s3-website-us-east-1.amazonaws.com/2015/tutorials/r8_watershed_transform.html
    """
    #Print the number of objects before the segmentation
    labels_pre_seg = measure.label(bw)    
    props_table_pre_seg = measure.regionprops_table(labels_pre_seg, properties=('area', 'coords')) 
    df_props_pre_seg = pd.DataFrame(props_table_pre_seg)
    if disp_details:
        print(f'\tFound in total {len(df_props_pre_seg)} objects before segmentation') 

    # Compute the distance transform of our above  
    distances = scipy.ndimage.distance_transform_edt(bw)

    # Find the local maxima. 
    # sz_obj_peak_local_max = 11
    # coords = peak_local_max(distance, footprint=np.ones((sz_obj_peak_local_max, sz_obj_peak_local_max)), labels=bw)
    local_max = skimage.feature.peak_local_max(distances, indices=False, footprint=None, labels=bw, min_distance=min_distance)

    # Label the markers.
    maxima = skimage.measure.label(local_max)

    # mask = np.zeros(distances.shape, dtype=bool)
    # mask[tuple(coords.T)] = True
    # markers, _ = ndi.label(mask)

    # Computer the watershed algorithm
    final_seg = skimage.segmentation.watershed(-distances, maxima, mask=bw)

    #Extract the region properties out of the objects
    props_table = measure.regionprops_table(final_seg, properties=('area', 'coords'))  

    #Convert the list of the region properties to a pandas data frame
    df_props = pd.DataFrame(props_table)
    if disp_details:
        print(f'\tFound in total {len(df_props)} objects after segmentation, split {len(df_props) - len(df_props_pre_seg)} objects.')  
    return final_seg


def create_post_array_mask_global_otsu(image_org, crop_border_width=1, area_max = 10000, 
                           small_obj_threshold = 410, otsu_radius = 50, dilate=False, 
                           dil_size=4, return_list_of_images=False, gauss_sigma=1,
                          border_width=1, border_val=0, otsu_threshold_factor = 1.0,
                          sz_closing=10):
    """
      Creates a mask of the post array (100x magnification)

            Parameters:
                    image_org (np array): fluorescent image (median) with posts
                    crop_border_width (float): width of the image border to crop away. Facilitates the processing to make sure no objects are combined at the edges.
                    area_max (float): Threshold that excludes all objects above this pixel area. This is to remove the channel area, 
                    where the fluid moves, that becomes very large after the image has been inverted.
                    small_obj_threshold (float): The threshold (in pixel area) for removal of small objects
                    otsu_radius (float): Radius of the disk to perform local otsu thresholding
                    dilate (bool): Will dilate the final objects if they would be too small.
                    dil_size (float): Size of the disk object to dilate with.

            Returns:
                    image_cropped (np array): Original image, but cropped to match the mask
                    mask (np array): Boolean mask of the post array
    """
    #Raise an exception if the image has the wrong dimensions.
    if len(image_org.shape) != 2:
        raise ValueError(f"The image must be 2D and not {len(image_org.shape)}D")
        
    if return_list_of_images:
        img_list = []
        titles_list = []

    if return_list_of_images:
        img_list.append(image_org)
        titles_list.append('Original image')     
    
    #Blur image with a Gaussian filter with the radius sigma
    #sigma = 2
    image_blurred = filters.gaussian(
        image_org, sigma=gauss_sigma)  
    if return_list_of_images:
        img_list.append(image_blurred)
        titles_list.append(f'After Gaussian blurring,\nsigma = {gauss_sigma}')     
        
    #Apply a Sobel Edge Detection filter
    image_sobel = filters.sobel(image_blurred)
    if return_list_of_images:
        img_list.append(image_sobel)
        titles_list.append('After Sobel Edge Detection')
        
    #Convert to uint8 (required for Local Otsu Thresholding)
    data = np.copy(image_sobel)
    data = data.astype(np.float64) / np.max(image_sobel) # normalize the data to 0 - 1
    data = 255 * data # Now scale by 255
    img = data.astype(np.uint8)

    # Threshold on edges
    threshold_level = filters.threshold_otsu(img)*otsu_threshold_factor
    bw_global_otsu = img > threshold_level # bw is a standard variable name for binary images  
    if return_list_of_images:
        img_list.append(bw_global_otsu)
        titles_list.append(f'After global Otsu thresholding,\nThreshold level = {threshold_level}')  

    #     #Binarization by Local Otsu thresholding
    #     selem = morphology.disk(otsu_radius) #disk object
    #     local_otsu = filters.rank.otsu(img, selem) #threshold mask
    #     bw_local_otsu = img >= local_otsu 
    #     if return_list_of_images:
    #         img_list.append(bw_local_otsu)
    #         titles_list.append(f'After local Otsu thresholding,\ndisk radius = {otsu_radius}')  
        
    #Remove the border by cropping as the posts don't extend to the image border
    bw_cropped = ff.crop_border(bw_global_otsu, b=crop_border_width)
    if return_list_of_images:
        img_list.append(bw_cropped)
        titles_list.append(f'After cropping image,\nwidth = {crop_border_width}')   
        
    # Clear any small object    
    #     bw_cleared = morphology.remove_small_objects(bw_blackborder, small_obj_threshold) # clear objects <400 px
    #     if return_list_of_images:
    #         img_list.append(bw_local_otsu)
    #         titles_list.append(f'After clearing small objects,\nthreshold = {small_obj_threshold}')      
            
    #Image inversion
    bw_inv = np.invert(bw_cropped)  
    if return_list_of_images:
        img_list.append(bw_inv)
        titles_list.append('Inverted image')    

    #Add a border
    #     bw_blackborder = ff.set_border_to_val(bw_inv, b=border_width, val=border_val)
    #     if return_list_of_images:
    #         img_list.append(bw_blackborder)
    #         titles_list.append(f'After adding a border of val={border_val} w = {border_width}')             
                

    #Extract the objects in the binary image
    labels = measure.label(bw_inv)
    
    #Extract the region properties out of the objects
    props_table = regionprops_table(labels, properties=('area', 'coords'))  
    
    #Convert the list of the region properties to a pandas data frame
    df = pd.DataFrame(props_table)

    #Filter maximum area allowed (to remove the gap spacing)    
    df = df[df['area'] < area_max]
    
    #Create a new image (cropped) to copy the coordinates from the adjusted objects data frame
    bw_no_big = np.zeros_like(bw_inv)
    
    #Add the objects to the new image, pixel coordinate by pixel coordinate
    coords = df['coords'].to_numpy()
    for obj in coords:    
        for [x,y] in obj:
            bw_no_big[x,y] = 1    
    if return_list_of_images:
        img_list.append(bw_no_big)
        titles_list.append('Removed large object')  

    #Dilate to match the real post size (optional)
    if dilate:
        bw_no_big = morphology.binary_dilation(bw_no_big, selem=morphology.disk(dil_size))
        if return_list_of_images:
            img_list.append(bw_no_big)
            titles_list.append(f'Dilated, disk size = {dil_size}')  

     #Morphological closing
    #sz_closing = 5
    bw_closed = morphology.binary_closing(bw_no_big, selem=morphology.square(sz_closing))
    if return_list_of_images:
        img_list.append(bw_closed)
        titles_list.append(f'After closing,\n square w = {sz_closing}')  


    #Rename the image to be returned        
    mask = bw_closed
    
    #Crop the original image as well
    image_cropped = ff.crop_border(image_org, b=crop_border_width)
    
    if return_list_of_images:
        masked_img = ff.mask_gray_img(image_cropped, mask, color=[255, 0, 0], alpha=0.2)
        img_list.append(masked_img)
        titles_list.append(f'Mask overlay')  
        
    print(f'\tCreating post array mask, image border cropped by {crop_border_width} pixels ')
    if return_list_of_images:
        return image_cropped, mask, img_list, titles_list
    else:
        return image_cropped, mask

def threshold_based_on_area(bw_inv, area_min_factor = 10, dir='min', based_on_median=True, disp_details=False):
    labels = measure.label(bw_inv)
    #Extract the region properties out of the objects
    props_table = measure.regionprops_table(labels, properties=('area', 'coords'))  
    #Convert the list of the region properties to a pandas data frame
    df = pd.DataFrame(props_table)

    areas = df.area.values
    if based_on_median:
        area_min = area_min_factor*np.median(areas)
    else:
        area_min = area_min_factor
    if dir == 'min':
        df = df[df['area'] > area_min]
    elif dir == 'max':
        df = df[df['area'] < area_min]
    if disp_details:
        print('median area', np.median(areas))
        print('area min', area_min)
        print('area_min_factor', area_min_factor)
        print('Data frame for creating the side wall mask')
        print(df.head(10))
    bw_side_walls = np.zeros_like(bw_inv)

    #Add the objects to the new image, pixel coordinate by pixel coordinate
    coords = df['coords'].to_numpy()
    for obj in coords:    
        for [x,y] in obj:
            bw_side_walls[x,y] = 1  
    return bw_side_walls


def find_side_walls(image_org, gauss_sigma=1, area_min_factor = 200, otsu_threshold_factor=0.5, disp_details=False, invert_img=True):
    """Find the side walls"""
    
    #area_min_factor before 120
    print('Finding the side walls')
    img_list = []
    titles_list = []
    img_list.append(image_org)
    titles_list.append('Original image')  

    #Blur image with a Gaussian filter with the radius sigma
    img_pre_blurred = copy.deepcopy(img_list[-1])
    image_blurred = filters.gaussian(
        img_pre_blurred, sigma=gauss_sigma)  
    img_list.append(image_blurred)
    titles_list.append(f'After Gaussian blurring,\nsigma = {gauss_sigma}')     

    #Apply a Sobel Edge Detection filter
    img_pre_sobel = copy.deepcopy(img_list[-1])
    image_sobel = filters.sobel(img_pre_sobel)
    img_list.append(image_sobel)
    titles_list.append('After Sobel Edge Detection')

    # Threshold on edges    
    img_pre_otsu = copy.deepcopy(img_list[-1])
    threshold_level = filters.threshold_otsu(img_pre_otsu)*otsu_threshold_factor
    bw_global_otsu = img_pre_otsu > threshold_level # bw is a standard variable name for binary images  
    img_list.append(bw_global_otsu)
    titles_list.append(f'After global Otsu thresholding,\nThreshold level = {threshold_level}')  

    if invert_img:
        #Image inversion
        bw_pre_inv = copy.deepcopy(img_list[-1])
        bw_inv = np.invert(bw_pre_inv)  
        img_list.append(bw_inv)
        titles_list.append('Inverted image')  

    bw_side_walls = threshold_based_on_area(img_list[-1], area_min_factor=area_min_factor, disp_details=disp_details)
    img_list.append(bw_side_walls)
    titles_list.append(f'After thresholding with area_min_factor = {area_min_factor}')  

    # bw_side_walls2 = threshold_based_on_area(bw_side_walls, area_min_factor=area_max_factor, dir='max')
    # img_list.append(bw_side_walls2)
    # titles_list.append(f'After thresholding with area_max_factor = {area_max_factor}')  

    return bw_side_walls, img_list, titles_list

import os
def show_histogram_local_conc(img, v, settings_general,c = 400):
    A = img.ravel()
    A = np.delete(A, np.where(A == 0))
    A_conc = c*A / A.mean()
    # max(A_conc)
    max_c = c*3
    print(f'max c = {max_c} ng/uL')
    fig, ax = plt.subplots(figsize=(15,4))
    plt.style.use('ppt') 
    plt.hist(A_conc,bins=256, color='b', range=(0,max_c),density=True)
    plt.title(f'Histogram of local concentration')
    plt.xlabel(r'C ($=<c> \cdot I / <I>) [ng/\mu L$] ')
    plt.ylabel('Normalized Frequency')  
    plt.style.use('general') 
    if settings_general['save_imgs']:
        file_name = f'histogram_frame'+str(v.frame_range[0])+'-'+str(v.frame_range[-1])+'_'+v.file_name0+'.png'
        path_save = os.path.join(v.dir_pixelated_files, file_name)
        plt.savefig(path_save, bbox_inches="tight") #Save figure 
        print(f'Saving Histogram plot at {path_save}')
    plt.show()
    plt.close(fig)

from mpl_toolkits.axes_grid1.inset_locator import inset_axes
def show_local_conc_img(img, v, settings_general, c=400):
    img2 = img[v.frame_to_display].copy()
    c = 400
    img2 = img2.astype(np.float32)
    A = img.ravel()
    A = np.delete(A, np.where(A == 0))
    img2 = img2 * c / A.mean()
    sz=10
    fig = plt.figure(figsize=(sz, sz))  # create a figure object
    ax = fig.add_subplot(1, 1, 1)  # create an axes object in the figure
    ax.axis('off')
    imgplot = ax.imshow(img2, cmap='inferno') 
    plt.style.use('general') 
    axins = inset_axes(ax,
                width="5%",  # width = 5% of parent_bbox width
                height="100%",  # height : 50%
                loc='upper right',
                bbox_to_anchor=(0.15, 0., 1, 1),
                bbox_transform=ax.transAxes,
                borderpad=0,
                )
    cbar = plt.colorbar(imgplot,ax=ax, cax=axins, orientation='vertical', label=r'c [$ng/\mu L$] ')
    if settings_general['save_imgs']:
        file_name = f'local_conc_frame'+str(v.frame_to_display)+'_range'+str(v.frame_range[0])+'-'+str(v.frame_range[-1])+'_'+v.file_name0+'.png'
        path_save = os.path.join(v.dir_pixelated_files, file_name)
        plt.savefig(path_save, bbox_inches="tight") #Save figure 
        print(f'Saving  plot at {path_save}')
    plt.show()   
    plt.close(fig) 
